/* Practica 5: Tecnología de la programación 
 * Alberto Terceño Ortega y David Álvarez Bobillo*/

package tp.pr5.mv;

import java.io.FileNotFoundException;
import java.util.Scanner; 

import tp.pr5.mv.asmexcep.ASMSyntaxErrorException;
import tp.pr5.mv.inout.InFile;
import tp.pr5.mv.inout.InFileWindow;
import tp.pr5.mv.inout.InKeyboard;
import tp.pr5.mv.inout.InMethod;
import tp.pr5.mv.inout.InNothing;
import tp.pr5.mv.inout.OutConsole;
import tp.pr5.mv.inout.OutFile;
import tp.pr5.mv.inout.OutMethod;
import tp.pr5.mv.inout.OutNothing;
import tp.pr5.mv.inout.OutWindow;
import tp.pr5.mv.window.TPMVWindow;

import org.apache.commons.cli.*;

public class Main {

	
	
	
	@SuppressWarnings({ "static-access", "unused" })
	public static void main( String []args){
		
		
		
		/* En primer lugar parseamos los argumentos de la línea de comandos y a partir de ahí
		 * entramos en la rama del programa correspondiente ( batch o interactive )  */
		
		String mode = "batch" , in = null , out = null , asm = null; // Strings necesarias para el parseo de argumentos
    	InMethod inMethod = null; // Flags necesarias para saber que metodos de entrada y salida utilizamos
    	OutMethod outMethod = null;
    	
    	Options options = new Options(); // Objeto que agrupa los argumentos posibles
		CommandLineParser parser = new BasicParser(); // Parseador de argumentos
		ProgramMV program = null; // Preparamos el programa de la maquina virtual para cargarlo si hemos especificado un fichero asm
		
		CPU cpuTest = null; // CPU utilizada en una de las ramas del programa.

		Scanner sc = new Scanner( System.in ); // Creacion del scanner para que el usuario teclee comandos
	    
		
	    try { 
			
	    	
			
			//Añadimos las opciones posibles
	    	
			options.addOption( OptionBuilder.withArgName( "asmfile" )
                    						.hasArg()
											.withLongOpt("asm")
                    						.withDescription(  "Fichero con el codigo en ASM del programa a ejecutar. Obligatorio en modo batch." )
                    						.create( "a" ) );
			options.addOption( OptionBuilder.withArgName( "infile" )
											.hasArg()
											.withLongOpt("in")
											.withDescription(  "Entrada del programa de la maquina-p." )
											.create( "i" ) );
			options.addOption( OptionBuilder.withArgName( "mode" )
											.hasArg()
											.withLongOpt("mode")
											.withDescription(  "Modo de funcionamiento (batch | interactive). Por defecto, batch." )
											.create( "m" ) );
			options.addOption( OptionBuilder.withArgName( "outfile" )
											.hasArg()
											.withLongOpt("out")
											.withDescription(  "Fichero donde se guarda la salida del programa de la maquina-p." )
											.create( "o" ) );
			options.addOption("h", "help", false, "Muestra esta ayuda");
			
			CommandLine cmdLine = parser.parse(options, args); // Parseamos en una CommandLine los argumentos introducidos
			
			// Vemos si se ha seleccionado la ayuda y terminamos el programa para que el usuario vuelva a introducir parametros de nuevo
			if ( cmdLine.hasOption("h") ) {
				
				HelpFormatter formatter = new HelpFormatter();
				formatter.printHelp( Main.class.getCanonicalName() , options, true ); 
                sc.close();
				return;
			}
			
			// Parseo del modo de la TPMV
			if ( cmdLine.hasOption("m") ) {
				mode = cmdLine.getOptionValue("m");
				if ( !mode.equals("batch") && !mode.equals("interactive") && !mode.equals("window") ) 
					sc.close();
					throw new ParseException("Modo incorrecto (parametro -m|--mode)"); 
			}
			
			// Establecemos por DEFECTO el in/out del batch y del modo interactive
			if ( mode.equals("batch") ){
				inMethod = new InKeyboard();
				outMethod = new OutConsole();		
			}
			else {   // Modo elegido: interactive o ventana.
				inMethod = new InNothing();
				outMethod = new OutNothing();
			}

					
			
			if ( cmdLine.hasOption("a") )   // Parseamos el archivo asm y vemos que se ha introducido un parámetro
				asm = cmdLine.getOptionValue("a");
		
			
			if ( mode.equals("batch") && asm == null || mode.equals("window") && asm == null ) // El modo batch debe especificar un archivo asm
				sc.close();
				throw new ParseException("Fichero ASM no especificado en el modo " + mode);					
			
			
			// Parseo de los archivos de entrada y salida
			if ( cmdLine.hasOption("i") ) {
				
				in = cmdLine.getOptionValue("i");
			
				try {
					inMethod = new InFile(in);   // Lanza excepción si no se encuentra el archivo.
				}
				catch ( FileNotFoundException ex) {
					System.err.println( "Uso incorrecto: Error al acceder al fichero de entrada (" + in + ")" );
			    	System.err.println("Use -h|--help para más detalles.");
			    	System.exit(1);
				}
			
			}
			
			
			
			if ( cmdLine.hasOption("o") ) {
				
				out  = cmdLine.getOptionValue("o");
				
				try {
					outMethod = new OutFile(out);     // Lanza excepción si no se encuentra el archivo.
				}
				catch ( FileNotFoundException ex) {
					System.err.println("Uso incorrecto: Error al acceder al fichero de salida (" + out + ")");
			    	System.err.println("Use -h|--help para más detalles.");
			    	System.exit(1);
				}
			}
			
			 
			
			
	    }
	    
	    catch ( ParseException ex ) {    // Cogemos todas las excepciones (errores) de parseo, mostramos su mensaje y terminamos la aplicación.
	    	System.err.println( "Uso incorrecto: " + ex.getMessage());
	    	System.err.println("Use -h|--help para más detalles.");
	    	System.exit(1);
	    }
		
	    
	    
	    // Ahora cargamos los programas.
	    
	    if( asm != null ) {

			 // Si hemos especificado un fichero lo cargamos en el programa de la MV
				
			try { // Vemos si existen errores de fichero no encontrado, entrada-salida o de error sintáctico
			     program = ASMLoader.loadASMFile( asm );
			}
		
		
			catch ( FileNotFoundException ex )
			{
				System.err.println( "Uso incorrecto: Error al acceder al fichero asm (" + asm + ")" );
		    	System.err.println("Use -h|--help para más detalles.");
		    	System.exit(2);
			}
			catch ( java.io.IOException ex )
			{				
				System.err.println( "Error al leer el archivo asm  (" + asm + ")" );
		    	System.err.println("Use -h|--help para más detalles.");
		    	System.exit(2);
			}
			catch ( ASMSyntaxErrorException ex )
			{
				System.err.println( ex.getMessage() );
		    	System.exit(2);
			}
			
	    }
	    
	    if ( mode.equals("interactive") && asm == null ) // Si no hemos especificado un archivo asm en el modo interactivo creamos un programa nuevo.
		{
			program = requestProgram(sc);
			showProgram( program ); // Enseñamos el programa al usuario
		}	    
	   
	    
	    cpuTest = new CPU( inMethod, outMethod ); // Creamos la CPU con los flags en el constructor
	    Controller c = null;
	    
	    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	    /* MODOS DE EJECUCIÓN DE LA MÁQUINA VIRTUAL*/
	    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	    
		if ( mode.equals("batch") ){ // BATCH
			
			
			// Ejecutamos el programa ya cargado
					
			c = new ControllerBatch(cpuTest);
			BatchView view = new BatchView();
			cpuTest.addObserver(view); 
		
		}
		else if ( mode.equals("interactive") ) {		// INTERACTIVO.	
			
			c = new ControllerInteractive(cpuTest, sc);
			InteractiveView view = new InteractiveView();
			cpuTest.addObserver(view);      
		
		}
		
		else { // VENTANA
			
			c = new ControllerSwing(cpuTest);
			
			TPMVWindow ventana = new TPMVWindow( c, cpuTest, cpuTest.getOperandStack(), cpuTest.getMemory() );
			
			// El outMethod y el inMethod de la CPU los inicializamos más tarde pues necesitamos la ventana 
			// en el caso de que sea modo "window".
			
			cpuTest.setOutMethod( new OutWindow( ventana, outMethod ) );
			
			if ( in != null ) {
				
				try {
					cpuTest.setInMethod(new InFileWindow( ventana, in) );   // Lanza excepción si no se encuentra el archivo.
				}
				catch ( FileNotFoundException ex) {
					System.err.println( "Uso incorrecto: Error al acceder al fichero de entrada (" + in + ")" );
			    	System.err.println("Use -h|--help para más detalles.");
			    	System.exit(1);
				}
				
			}
			
			ventana.setVisible(true);	
		}
		
		
		cpuTest.loadProgram( program ); // Cargamos el programa en CPU		
		c.execute();
		
		sc.close();
					
	}
	    

			
	
	
	
	public static void showProgram( ProgramMV program ) { // Método que muestra por pantalla las instrucciones
		//de las que está compuesta el programa de la MV
		
		System.out.println( "El programa introducido es:");
		
		for ( int i = 0; i < program.getNumInstrs(); i++ ) 
			
			System.out.println( i + ": " + program.getInstrAt(i).toString() );
		
	}
	
	public static ProgramMV requestProgram(Scanner sc) { // Metodo que pide al usuario que introduzca un programa
		
		ProgramMV program = new ProgramMV();
		Instruction instr = null;
		String line = null;
		
		System.out.println( "Introduce el programa fuente");
		System.out.print( "> ");
		line = sc.nextLine(); // Tomamos la linea escrita por el usuario
			
		while ( !line.equalsIgnoreCase("END") ) { // Mientras no escribamos END, el programa seguirá pidiendo instrucciones
			
			instr = InstructionParser.parse( line ); // Parseamos la línea, que devolverá una instrucción
				
				
			if ( instr == null) // Si lo que ha devuelto el parseador es null, entonces la instrucción es incorrecta
				
				System.out.println( "Error: Instrucción incorrecta" );
			else 
				program.addInstruction(instr); // Si no, añadimos la instrucción al programa
					
			System.out.print( "> ");
			line = sc.nextLine(); // Tomamos la linea escrita por el usuario
		
			line = line.trim(); 
		}

		
		return program;
		
	}
	
	
	
}


